Conversation
…INATION opt-out The pagination field now defaults to make_pagination_dep() (page 50, max 200) so a derived list route is bounded by default; None is dropped from the public field type and survives only as api.py's internal route-shape switch. A new NO_PAGINATION sentinel (mirroring UNGUARDED) is the single explicit opt-out, resolved to None once at the spine boundary so api.py is unchanged. The archives, checksums, and snippets list routes flip from a plain list to a PaginatedResponse envelope; backup_pg/backup_mongo drop their now-redundant pagination declarations (effective limit unchanged).
The snippets derived list route now returns a PaginatedResponse envelope by default. useSnippets has a bespoke fetch path (snippets is a CUSTOM_APP_REGISTRY entry) that bypasses the generic unwrapTasks helper archives/checksums route through, so it returned the raw envelope and the list page crashed on for...of. Unwrap both the envelope and the legacy flat-array shape so the cached query data stays SnippetResponse[] for every consumer.
There was a problem hiding this comment.
Pull request overview
This PR updates the SEP plugin framework’s TaskExecutionApp so derived GET / list routes paginate by default (default limit 50, max 200), with a single explicit opt-out sentinel NO_PAGINATION. This bounds list responses for newly added task apps without requiring each app to opt in, and updates affected backend tests, OpenAPI snapshots, and frontend consumers (notably the bespoke snippets app) to handle the paginated envelope.
Changes:
- Make
TaskExecutionApp.paginationdefault tomake_pagination_dep()and introduceNO_PAGINATIONas the explicit opt-out, resolved toNoneat the spine boundary. - Update affected apps/tests/snapshots to reflect list response shape flipping from
list[...]toPaginatedResponse[...]for archives/checksums/snippets. - Update the snippets frontend hook to unwrap either the legacy flat array or the new paginated
{ items, ... }envelope, with regression tests.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| app/sep/apps/framework/apps.py | Defaults derived list routes to paginated; adds NO_PAGINATION sentinel and resolves it in build_router. |
| app/sep/apps/backup_pg/app.py | Removes redundant explicit pagination=make_pagination_dep() now covered by the framework default. |
| app/sep/apps/backup_mongo/app.py | Removes redundant explicit pagination wiring now covered by the framework default. |
| changelog.d/SEP-1564.changed.md | Documents the default pagination behavior and list response shape changes. |
| tests/app/sep/apps/framework/test_apps.py | Extends framework tests for default pagination vs NO_PAGINATION for CRUD and script-flavored apps; adds pagination=None rejection coverage. |
| tests/app/sep/apps/framework/test_api.py | Adds end-to-end HTTP tests for paginated script list routes and a helper to mount script routers. |
| tests/app/sep/apps/framework/test_scaffold.py | Updates scaffold expectations to accept paginated list envelopes by default. |
| tests/app/sep/apps/framework/test_script_source.py | Updates script-source derived-route HTTP tests for default envelope and NO_PAGINATION opt-out. |
| tests/app/sep/apps/framework/kit.py | Adds a synthetic script-flavored app builder to exercise pagination behavior end-to-end in tests. |
| tests/app/sep/apps/framework/contract_suite.py | Updates contract suite branching to use NO_PAGINATION instead of None for list-shape expectations. |
| tests/app/sep/apps/snippets/test_api_routes.py | Updates snippets API list tests to unwrap items from the paginated response. |
| tests/app/sep/apps/archives/test_api.py | Updates archives list test to unwrap items from the paginated response. |
| tests/app/sep/snapshots/openapi/archives.json | Regenerates OpenAPI snapshot reflecting paginated list envelope + params. |
| tests/app/sep/snapshots/openapi/checksums.json | Regenerates OpenAPI snapshot reflecting paginated list envelope + params. |
| tests/app/sep/snapshots/openapi/snippets.json | Regenerates OpenAPI snapshot reflecting paginated list envelope + params (and 422 due to query validation). |
| frontend/packages/apps/snippets/src/hooks.ts | Updates useSnippets to unwrap either legacy array or paginated envelope to keep consumer shape stable. |
| frontend/packages/apps/snippets/src/hooks.test.tsx | Adds tests covering both envelope and legacy-array responses for useSnippets. |
| frontend/packages/api/specs/sep.json | Updates aggregated OpenAPI spec to reflect pagination changes. |
| frontend/packages/api/src/generated/sep.ts | Updates generated TypeScript types/operations to match the updated OpenAPI. |
Automated QA — PASSVerified the unchecked Tested items against a fresh instance on the PR head. The shared
Observations — pre-existing, out of scope
|
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Tracked the follow-up from the PR description as SEP-1567: Add load-more/pagination controls to app list pages. |









Summary
Default the shared
TaskExecutionAppframework spine's derived list route to paginated, with aNO_PAGINATIONsentinel as the single explicit opt-out — so a new task app gets bounded list responses without opting in.app/sep/apps/framework/apps.py): thepaginationfield now defaults tomake_pagination_dep()(page size 50, ceiling 200) instead ofNone.Noneis dropped from the public field type; a newNO_PAGINATIONsingleton (mirroring the existingUNGUARDEDguard sentinel — same__slots__/__repr__shape) is the single opt-out. The sentinel is resolved toNoneonce at the spine boundary inbuild_router, so the internal route-derivation layer (api.py) is unchanged and keeps keying its plain-list vs envelope branch on that internalNone.archives,checksums, andsnippetslist routes (which omit the field) auto-flip from a plainlist[model]to aPaginatedResponse[model]envelope. Regenerated OpenAPI snapshots, the aggregate API spec, and the generated TypeScript types capture the flip; the 5 already-paginated apps' specs are unchanged.backup_pgandbackup_mongodrop their now-redundantpagination=declarations. Their effective limit (page 50 / ceiling 200) is identical to the new default, so behavior is preserved.Frontend consumer update (outside the original plan's scope)
Most app list pages read their list route through the generic
useAppTasks/useAppEntityListhooks, which already unwrap both the flat-list and paginated-envelope shapes — that coversarchivesandchecksums.snippets, however, is a bespoke app with its ownuseSnippetshook that fetched the list route and returned the body verbatim. Once the route paginates, that hook would hand the list page an envelope object where it expects an array, crashing the page.useSnippetsnow unwraps both shapes, so the cached query data staysSnippetResponse[]for every consumer (including the optimistic approve/unapprove mutations). A hook-level regression test exercises the real HTTP→JSON path for both the envelope and legacy-array shapes.Tests
Default-on and
NO_PAGINATIONpaths are covered for both CRUD-flavored and script-flavored apps, plus apagination=Nonerejection test, a script-flavored paginated over-HTTP test, and a contract-suite branch fix (keys on the sentinel instead ofNone). Per-app list-shape assertions for the flipped apps were updated to unwrap the envelope.Tested
GET /api/apps/archives/returns a{items, total, offset, limit}envelope;GET /api/apps/archives/?limit=1returns a single-item page.GET /api/apps/snippets/?offset=1returns the next page (or an emptyitemslist past the end) without error.Known limitations
archives/checksums/snippetspaginate, lists longer than 50 rows are truncated to the first page in the UI. This already affects the 5 previously-paginated apps, so the change makes the behavior consistent rather than introducing a new class of truncation; wiring a load-more/pagination control is out of scope here and tracked as a separate follow-up.Checklist
Database migrations generated if models changed ((N/A — no model changes)make makemigrations)Configuration changes documented with examples(N/A — no configuration changes)